package se392.ateam2006.resourcemgmt.participant.exclusionset;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Calendar;
import java.util.Arrays;

/**
 * This class is used to resolve a time for a meeting which suits the needs of all the participants
 * The operations are designed to fulfill the requirements of Story 21
 * @author Ateam (John Adderley, Claire Melton, Matthew Bennett, Shingai Manyiwa)
 * @version 15/03/07
 */
public class MeetingTimeResolutionUtility {
    
    // Constant to define the size of a set (which is the number of 10 minute intervals in a day)
    public static final int SET_SIZE = 144;
    
    // Collection to hold all of the ExclusionRangeEntity objects we're going to deal with
    private ArrayList<ExclusionRangeEntity> exSets;
    
    /**
     * Default constructor if we want to create an empty MeetingTimeResolutionUtility object
     */
    public MeetingTimeResolutionUtility() {
        
        // Initialise the exSets collection as an empty ArrayList
        exSets = new ArrayList<ExclusionRangeEntity>();
    }
    
    /**
     * Create a new MeetingTimeResolutionUtility object
     * @param sets - a collection of ExclusionRangeEntity objects
     */
    public MeetingTimeResolutionUtility(ArrayList<ExclusionRangeEntity> sets) {
        
        exSets = sets;
    }
    
    /**
     * Add a new ExclusionRangeEntity object to the collection
     * @param set - the new ExclusionRangeEntity
     */
    public void addRange(ExclusionRangeEntity set) {
        
        exSets.add(set);
    }
    
    /**
     * Remove an ExclusionRangeEntity from the collection
     * @param set - the ExclusionRangeEntity to remove
     * @return boolean - was the range removed?
     */
    public boolean removeRange(ExclusionRangeEntity set) {
        
        return exSets.remove(set);
    }
    
    /**
     * Given each participant's exclusion/preference sets, attempt to resolve a time that suits everyone
     * @param duration - the duration (in minutes) that the meeting is required for
     * @return Calendar - the resolved meeting start time
     * Alternatively, if we can't resolve a satisfactory time, return null and deal with it elsewhere
     */
    public Calendar resolveTimes(int duration) {
        
        // First off, let's check that each of the individual sets contains enough free time
        // Otherwise, we don't need to do anything clever as there's no point
        if(!(ensureSufficientTimeInAllRanges(duration))) return null;
        
        // Ok, if we're here then it may be possible to resolve a time. Let's do some more work
        int[] overlaps = generateRangeOverlaps();
        
        // Find how many 10 minute blocks the duration covers (want to round this up)
        int durationMask = (int)Math.ceil(duration / 10);
        
        // Variables to store the resolved start and end times (if we can find them...)
        int resStart = findResolvedStartTime(overlaps,durationMask);
        System.out.println("Resolved start time = " + resStart);
        
        // Now check to see if we managed to find a good start and end time
        if(resStart != -1) {
            int resEnd = resStart + durationMask;
            // Get the start date of the first exclusion set to give us a good starting point for our output date
            Calendar tmpCal1 = exSets.get(0).getStartDate();
            
            // Calculate the start date and time from the resStart variable
            int start = resStart * 10;
            System.out.println("Start in mins = " + start);
            int startHour = (int)Math.floor(start / 60);
            System.out.println("Start hour =" + startHour);
            int startMin = (start % 60);
            System.out.println("Start minute =" + startMin);
            // Set the required fields in the calendar
            tmpCal1.set(Calendar.HOUR_OF_DAY, startHour);
            tmpCal1.set(Calendar.MINUTE, startMin);
            // Put it in the output array
            System.out.println("cal 1 time =" + tmpCal1.get(Calendar.HOUR_OF_DAY) + ":" + tmpCal1.get(Calendar.MINUTE));
            
            // Ok, we're done. Return the array of results
            return tmpCal1;
        }
        
        // Despite our amazing, and frankly heroic efforts, we were unable to resolve a time for the meeting... tsk.
        return null;
    }
    
    /**
     * Resolve a start time for a meeting (returns an index in an array (i.e. actual time is
     *      the returned value * 10
     * @param overlaps - an int[] representing the overlaps off all of the exclusion ranges
     * @param durationMask - int, the ammount of free time we need to find
     * @return int - the resolved start time for the meeting (or -1 if we couldn't resolve)
     */
    private int findResolvedStartTime(int[] overlaps,int durationMask) {
        
        // Look for a match with just preference sets
        for(int i=0; i<=SET_SIZE-durationMask; i++) {
            if(overlaps[i] == 1) {
                System.out.println("preference found at i=" + i);
                for(int j = i+1; j<(i+durationMask);j++) {
                    if(overlaps[j] != 1) {
                        System.out.println("but it's not long enough");
                        break;
                    }
                }
                System.out.println("found preferred space at i=" + i);
                return i;
                
            }
        }
        
        // If we couldn't match just preference sets, look for a match
        // with anything that is not an exclusion set
        for(int i=0; i<=SET_SIZE-durationMask; i++) {
            if(overlaps[i] !=-1) {
                System.out.println("Non-exclusion found");
                for(int j = i+1; j<(i+durationMask); j++) {
                    if(overlaps[j] == -1) {
                        System.out.println("but it's not long enough");
                        break;
                    }
                }
                System.out.println("found space at i=" + i);
                return i;
            }
        }
        
        // We failed... return an error code
        return -1;
    }
    
    /**
     * Create an array of ints representing all of the preference and exclusion sets overlayed
     * @return int[] - the result of the overlay
     */
    private int[] generateRangeOverlaps() {
        
        int[] out = new int[SET_SIZE];
        
        // Fill the output array with 1's (see loop later)
        Arrays.fill(out, 1);
        
        // Loop round each index in the array
        for(int i=0; i<SET_SIZE; i++) {
            
            // Loop round each of the exclusion/preference sets
            for(ExclusionRangeEntity ex : exSets) {
                
                //if anything in this column is less than the current value in our output array, make
                //the value in the output array equal to it. So, any -1's in a column will make the output
                //for that column -1, if not, then any 0's will make tha output 0, else the output will be 1
                if(out[i] > ex.toIntArray()[i]) {
                    out[i] = ex.toIntArray()[i];
                }
            }
        }
        
        System.out.println(Arrays.toString(out));
        return out;
    }
    
    /**
     * Ensure that there is sufficient free time available in all of the exclusion ranges
     * @param duration - the duration of the required meeting (in minutes)
     * @return boolean - true if there is enough time, false if not
     */
    private boolean ensureSufficientTimeInAllRanges(int duration) {
        
        // Loop round each exclusion range and check them
        for(ExclusionRangeEntity row : exSets) {
            
            if(!(ensureSufficientTime(row, duration))) return false;
        }
        
        return true;
    }
    
    /**
     * Perform a quick test to ensure that there is enough free time in an exclusion range to allow for a meeting
     * @param range - the exclusion range
     * @param duration - the amount of time that the meeting needs to be for (in minutes)
     * @return boolean - true if there is enough time, false if not
     */
    private boolean ensureSufficientTime(ExclusionRangeEntity range, int duration) {
        
        // Get the start and end times in minutes
        int start = range.getStartTimeInMins();
        int end   = range.getEndTimeInMins();
        
        // Check if there's enough time for the meeting
        return ((end - start) < duration) ? false : true;
    }
}
